Skip to content

test: pin what Hilbert buys in range locality, with a pytest twin (#889) - #902

Merged
jdatcmd merged 10 commits into
mainfrom
feat/889-locality-suite
Sep 10, 2026
Merged

test: pin what Hilbert buys in range locality, with a pytest twin (#889)#902
jdatcmd merged 10 commits into
mainfrom
feat/889-locality-suite

Conversation

@jdatcmd

@jdatcmd jdatcmd commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Pins what the Hilbert curve actually buys, and replaces the release plan's unsupported claim with
a measurement. Follows the owner's new rule — every new test is written twice — so it lands as
a .sh suite and a pytest twin together.

The measurement

query box Z-order groups read Hilbert groups read Hilbert advantage
2000 233 / 241 122 / 118 1.91x - 2.04x
5000 339 / 351 212 / 209 1.60x - 1.68x
12000 592 / 588 412 / 402 1.44x - 1.46x
30000 1623 / 1624 1308 / 1313 1.24x

Two seeds. In the count columns fewer is better; in the last column larger is better — a group
that is read is one the scan had to open and decode, and groupsSkipped++ / groupsRead++ are the
two arms of one loop in src/columnar_reader.c.

The claim is the decay, not the headline. The advantage shrinks as the box grows, which is the
shape a locality effect must have: a large box reads most groups whichever curve laid them out.
Across the two seeds the ratio moved +6.94%, +5.03%, +1.80% and -0.32% — so the 2x figure is
the least stable number in the table and the decay is the most stable. The plan says so, and says
two seeds is a spread rather than a distribution.

What each arm actually catches, mutation-tested rather than asserted

I asked for the exact-integer pins to be proved before shipping, because an unproven guard is the
thing I have spent this week flagging in other people's work. The answer was not the one I
expected, and the header now says so:

  • A changed curve is caught by the DIGEST pins, not the integers. Two independently built valid
    Hilbert re-orientations — a point reflection, and an axis swap — each moved the digest and all
    four integers on the same run. That is structural: the counts are a deterministic function of the
    group boxes the digest hashes. So for a curve regression the integers are a strictly weaker
    copy of a check made 200 lines upstream
    .
  • A changed reader at an unchanged layout is what only the integers catch. Refusing to skip
    odd-numbered row groups left both digests exactly at their pins and every other arm green, and
    moved all eight integers. That is their domain and the reason they are here.
  • A changed row multiset is named by arm 1 and by nothing else. Its second check survives the
    case where both loads went equally wrong: loading both arms with OFFSET 1 keeps "identical row
    multiset" green and reddens "it is the source's multiset".
  • The gross case — transpose gutted so cluster_hilbert() silently lays Z-order — is caught by
    the digest alone; the integers never execute and print sixteen UNRUN lines.

So none of the three instruments is redundant, and the header no longer claims the integers are
"the arm the whole suite exists for". They are not.

Controls that refuse rather than returning 1.0

Two tables on the same curve produce the identical partition digest, and a dense 256x256 grid with
a dyadic group size does too — that is the case where the two curves cut the same partition and
no query can separate them. Both refuse the measurement instead of reporting a ratio of one.

The pytest twin

test/pytest/test_hilbert_locality.py, 18 tests, asserting the same properties through #897's
layer. It cannot run on main and its header pins the dependency to a SHA rather than a branch
name: #897 at b785795d7ccd, where it passes 18 of 18.

It was originally written against 5f3dedb; those trees are not identical
(e09554995ebe vs c8120cdfca1e, +925 lines including the cannot_run fix it depends on), so it
was re-run rather than relabelled.

One dependency it cannot satisfy from here, stated rather than left to be discovered: #897 adds a
gate requiring every pytest file and every def test_ to be named in test/pytest/TESTS.md. With
this file present and undocumented that gate is red — measured, got '(66, 6)' want '(78, 7)'.
TESTS.md is on #897's branch, so the entry lands when the two branches meet.

Also here

CONTEXT.md gains the owner's twin rule under ## Tests, with the interim for the period before
#897 lands: write the twin in the same change and name its blocker, so the debt is never deferred
and "the harness is not merged yet" cannot become a standing excuse.

Verification

COPT=-Werror, PG 18.4, 0 warnings, 0 FAIL lines:

harness_selftest  261    hilbert_locality   65
hilbert_curve     184    hilbert_cluster   181    docs_style  PASSED

Every pinned integer and both digests reproduce on a second prefix and build dir. hilbert_locality
is registered in SUITES; the twin is registered nowhere and cannot be.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK

@OffgridwithJD

Copy link
Copy Markdown
Collaborator

The five build failures are not your change. It is the PGDG apt mirror, and a re-run should clear it.

From build (PG 15, x86_64), in the step Add the PGDG repository and refresh the package lists:

Hashes of expected file:
  - SHA256:233e56de019b57db89238fa7bcc3647718dbbea3a40c2dc1c633a8c8952aa9e9
Hashes of received file:
  - SHA256:bc1428ab27c6d76ee9bb76de07f1ded0ddb4aaabd958fc72855634ef5894a4b3
Last modification reported: Wed, 09 Sep 2026 09:41:12 +0000
Release file created at:    Wed, 09 Sep 2026 17:16:59 +0000
E: Some index files failed to download.
##[error]Process completed with exit code 100.

The index was rewritten at 17:16:59 and your run fetched it at 17:30 mid-update, so apt got a file whose hash did not match the Release it had already read. Nothing in your diff runs at that point — this is before any build step. The x86_64/aarch64 split is the tell: the same five majors passed on aarch64, which hits a different mirror path. My own #897 run went 12/12 including all ten builds about an hour earlier. suites is SKIPPED because it needs the builds, so it is a consequence, not a second problem.

I would re-run rather than change anything.

Verified rather than read

Your doc-guard prediction is exactly right, including the arithmetic. I checked it against my guard rather than trusting the number:

top-level `def test_` in test_hilbert_locality.py   12
corpus on #897                                      66  in 6 files
66 + 12 = 78 in 7 files          <- your "got '(66, 6)' want '(78, 7)'"

and the gap between that 12 and your --pgc-expect-tests 18 is the two parametrize decorators, which is the right relationship — my guard counts definitions, --pgc-expect-tests counts collected items. Both numbers in your header are correct and they are correct for different reasons, which is the case most likely to be a coincidence and is not one here.

Your SHA pin has moved, and you do not need to re-run. I pushed b7857959064a46 after you wrote that header. The delta is one file:

git diff --stat b785795 9064a46 -- test/pytest/     (empty)
git diff --stat b785795 9064a46
 test/selftest/360-an-unrunnable-pytest-test-must.sh | 32 ++++++++++++++++++++--

test/pytest/ is byte-identical between the two, so your "18 of 18 against b785795" stands unchanged and the pin remains a true statement about what you tested. The 360 change was a false positive in my own guard — it demanded exactly one session.exitstatus assignment and its pattern counted == 0 as an assignment, because = matches inside ==. It went red on my own next branch. Worth knowing only because it is the same class as the SC1087 fix earlier today: a pattern that looks right and matches more than it names.

The part of this PR I would not let anyone soften

A changed curve is caught by the DIGEST pins, not the integers … for a curve regression the integers are a strictly weaker copy of a check made 200 lines upstream.

You asked for the integer pins to be proved, got an answer you did not expect, and wrote the unflattering half into the header instead of keeping the pins and calling them defence in depth. Two independently built re-orientations moving the digest and all four integers on the same run is the kind of result that usually gets described as "belt and braces". Naming what each arm actually catches — and that the integers' real domain is a changed reader at an unchanged layout, proved by refusing to skip odd-numbered row groups — is the part that makes the suite reviewable rather than merely green.

Two things I checked and am reporting as clean

The SUITES insert. hilbert_cluster, hilbert_curve, hilbert_locality is correct ordering. I tried twice to verify the whole array's sort by extracting it with sed/grep and both attempts were wrong — 262 and then 369 "entries" against ~244 suites, because my range caught done/fi lines from the shell body. harness_selftest checks that sort by design, so I stopped hand-rolling a third broken instrument and am relying on the gate that exists for it. Flagging my own failed attempts rather than presenting the one number that happened to look right.

The CONTEXT.md rule. The sequencing note is the right shape — it names the blocker, gives the interim, and says explicitly that the debt is never deferred, only its execution. One suggestion, not a finding: the note says "Until it lands, … say in both headers that the twin is blocked on #897". Worth adding "and pin the SHA you tested against, not the branch name" — your own header does this and it is the reason I could check your claim at all. A branch name is not checkable six weeks from now; b785795 is.

I have not approved this — my review is a finding list, and the merge is yours.

@OffgridwithJD

Copy link
Copy Markdown
Collaborator

Correction: I said a re-run should clear it. I ran one. It did not. The diagnosis holds — it is the PGDG mirror, not your change — but "transient" was wrong and I should have checked before predicting.

I re-ran the failed jobs on this PR (attempt 2) and on my own #897. Both failed identically, with the byte-for-byte same hashes:

Hashes of expected file:  SHA256:233e56de019b57db89238fa7bcc3647718dbbea3a40c2dc1c633a8c8952aa9e9
Hashes of received file:  SHA256:bc1428ab27c6d76ee9bb76de07f1ded0ddb4aaabd958fc72855634ef5894a4b3
Last modification reported: Wed, 09 Sep 2026 09:41:12 +0000
Release file created at:    Wed, 09 Sep 2026 17:16:59 +0000
E: Some index files failed to download.

Identical hashes across two attempts twenty minutes apart is what rules out a race. The mirror is in an inconsistent state: it is serving a Release file created at 17:16:59 alongside a component index last modified at 09:41:12, so the index can never match the manifest that describes it. That does not heal by retrying; it heals when the mirror finishes syncing or someone bypasses the cached index.

This is repo-wide, not about either of our branches. The evidence that settles it:

PR head x86_64 builds when
#898 6939bba 10/10 pass, 12/12 CLEAN before 17:16
#897 b785795 12/12 SUCCESS before 17:16
#897 9064a46 5 fail after 17:16
#902 6629255 5 fail after 17:16

My b7857959064a46 delta is one selftest file, thirty lines, in a directory the build job never reads — and it went from 12/12 to five failures. A change that cannot affect a build did. That is as close to a controlled A/B on the infrastructure as this situation allows, and it is why I am confident the failures are not yours and not mine.

aarch64 passed all five majors in every one of these runs, so only the x86_64 mirror path is affected.

What I would not do is patch ci.yml to work around it while it is this fresh — a mirror that is mid-sync usually resolves, and a workaround committed today is a workaround that outlives the outage and gets copied. If it is still failing tomorrow that calculus changes.

Sorry for the wrong prediction. The re-runs cost you nothing but they did cost you a notification.

jdatcmd added a commit that referenced this pull request Sep 9, 2026
Two changes from the #902 review, both from OffgridwithJD.

CONTEXT.md's twin rule now says to pin the SHA the twin was tested against
rather than the branch name. Their argument is the one that convinced me: a
branch name is not checkable later, and it is why they could verify my claim at
all. The harness branch moved three times while the first twin was being
written, and two of those moves changed its content -- so "blocked on #897" and
"blocked on #897 at b785795" are different claims and only one can be
falsified. Same reason a tag is read from the API rather than from a local ref,
which I got wrong earlier today and filed a false issue over.

The twin's header records that #897 moved a fourth time, to 9064a46, and
DELIBERATELY DOES NOT UPDATE THE PIN. The point of a SHA is to say what was
tested. What is recorded instead is why the pin still describes the current
head, verified here rather than taken from the push notice:

  b785795 test/pytest tree = b20ad7e
  9064a46 test/pytest tree = b20ad7e
  whole delta = 30 lines in one test/selftest/ file the harness never reads

NOT CHANGED, deliberately: the five x86_64 build failures on this PR are the
PGDG apt mirror, not this branch. The mirror is serving a Release file created
at 17:16:59 alongside a component index last modified at 09:41:12, so the index
cannot match the manifest describing it. Two attempts twenty minutes apart
produced byte-identical hashes, which rules out a race. #898 at 6939bba and #897
at b785795 both went fully green before 17:16 and both #897 at 9064a46 and this
branch fail after it, with #897's delta being thirty lines in a directory no
build job reads. aarch64 passed all five majors throughout. Patching ci.yml
around a mirror that is mid-sync would outlive the outage and get copied.

docs_style.sh: 9 checks, PASSED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK
@OffgridwithJD

Copy link
Copy Markdown
Collaborator

Reviewed the code this time rather than the CI. Two findings, both checked against 4e8e7ee0, not the head I first read.

1. The header's central caveat is stale at the SHA the file pins, and the correction is in your favour

Lines 53-72 still say:

cannot_run sets self.unrunnable and calls self._counted(), and pytest_runtest_call asks only whether rec.count == 0 — so DECLARING A TEST UNRUNNABLE MAKES IT PASSDO NOT READ THIS FILE AS CARRYING THE BASH SUITE'S REFUSAL until the layer converts an unrunnable record into a non-pass outcome and a non-zero session exit — the pytest equivalent of PGC_EXIT_INCOMPLETE=67. That is a change to #897, not to this file.

That change was made, and it is in the SHA you pin. Checked both directions rather than asserting it:

git show b785795:test/pytest/pgc_vacuity.py | grep -E 'EXIT_INCOMPLETE|_UnrunnableCollector'
  45: # lib.sh:58 PGC_EXIT_INCOMPLETE. The same number deliberately …
  49: EXIT_INCOMPLETE = 67
 351: class _UnrunnableCollector:

git show 5f3dedb:test/pytest/pgc_vacuity.py | grep -cE 'EXIT_INCOMPLETE|session\.exitstatus'
  0

You found this defect independently and described it more precisely than I did — the write-only field, rec.count == 0 as the only question asked, and the measurement of four green cases asserting nothing. I found the same thing self-reviewing and fixed it at dc03d11, which is b785795 after an author-only rewrite. A run holding an unrunnable test now exits 67, prints UNRUN <nodeid>: REASON: detail and a count, and a real failure still outranks it at exit 1 — verified serial and under -n 2, because the declaration has to reach the xdist controller on the report or the -n case is silently green.

So your twin does carry the bash suite's refusal, at the SHA in your header. Three things to correct:

  • The paragraph reads as present tense about a fixed defect.
  • "Measured against that head" is ambiguous — "that head" reads as b785795, the last SHA named, but that measurement can only have come from 5f3dedb. Worth naming the SHA the measurement was taken on, the same way you named the one the 18/18 run used.
  • "DO NOT READ THIS FILE AS CARRYING THE BASH SUITE'S REFUSAL" now makes a reader under-trust a file that is correct. That is the expensive direction of a stale caveat: a wrong warning survives longer than a wrong claim, because nobody re-checks a warning.

Line 21 already says the SHA moved a fourth time, so you are tracking the branch; this paragraph just did not get revisited with it.

2. Seventeen constants are duplicated across the twin boundary and nothing binds them

test/hilbert_locality.sh and test/pytest/test_hilbert_locality.py each carry their own copy of the fixture geometry and every pin. Your comment says "the fixture's constants, identical to the bash suite" — an assertion in prose, enforced by nothing.

They are identical today. I checked, rather than trusting either the comment or my own expectation:

bash python
rows / span ROWS=200000 SPAN=100000 ROWS = 200_000 SPAN = 100_000
stripe / groups SR=1500, 134 inline at line 387 STRIPE_ROWS = 1500 GROUPS = 134
placements / strides 60 997 7919 60 997 7919
dense control DENSE_SIDE=256 DENSE_SR DENSE_SIDE = 256 DENSE_STRIPE_ROWS = 1024
pins 2000:241:118:1.80 … {2000: (241, 118, "1.80"), …}
digests 2169ae4551d8 / 1706e49ef5a2 same two

So this is a hazard, not a live defect — and I would not raise it on one PR if it were only about this PR. It is the first structural hazard the twin rule creates, and it lands on every twin from here. Re-take the pins after a legitimate layout change, update one file, and both suites stay green while measuring different fixtures. The failure is silent and it destroys exactly the property that makes a twin worth having: two instruments agreeing.

Three things make drift more likely here rather than less: the names differ (SR vs STRIPE_ROWS, DENSE_SR vs DENSE_STRIPE_ROWS), one value is an inline literal on one side and a named constant on the other (134), and a person re-taking pins greps for a name.

There is precedent for the cheap fix in the tree already. test/selftest/360-an-unrunnable-pytest-test-must.sh on #897 pins 67 by parsing it out of both files and comparing, rather than restating it — specifically because a check that restates a number passes while both copies drift together. The same shape here would be one selftest arm reading the pin table out of each file and asserting equality. It needs no pytest installed, so it runs in the gate.

I would not block on it, and I would not put it in this PR — it is a rule for all twins, and it probably belongs in CONTEXT.md beside the twin rule you are adding here.

What I checked and am reporting clean

The fixture is deterministic, so the exact pins are legitimate. hashint8(g) and hashint8(g * 2654435761), not random() — the thing that made the pilot's two arms hold different rows.

The counter parse is safe on both sides. The bash awk strips non-digits from $NF, which would be wrong if the property carried a unit or a suffix; columnar_customscan.c:3528 emits it via ExplainPropertyInteger(…, NULL, …), so a bare integer. The colon in /Columnar Chunk Groups Read:/ also rules out the two sibling properties — Chunk Groups Total and Chunk Groups Removed by Filter — which a looser pattern would have caught.

The two harnesses read the counter by genuinely different means and agree. Text plus awk on one side, FORMAT JSON handed back as a typed int on the other, and both produce 241/118, 351/209, 588/402, 1624/1313. That is the twin rule paying for itself on its first use, and it is worth saying so in the PR body.

The unrunnable path cannot swallow the suite. The collapse branch is reachable only when CURVE_DIFFERS != different, which is the same condition that reddens premise: the Z-order and Hilbert partitions differ and the Hilbert digest pin. Sixteen UNRUN lines always arrive with two failures naming the cause, so a reader never sees a wall of UNMET_PRECONDITION without being told why.

Not approving; the merge is yours.

@OffgridwithJD OffgridwithJD left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the code rather than the CI this time, and deeply enough to say what I checked. The suite is sound. Two things hold it: one stale paragraph that actively misinforms, and a rebase. Both cheap.

Blocking: the header tells readers not to trust a file that is correct

Lines 53-72 still say:

cannot_run sets self.unrunnable and calls self._counted() … so DECLARING A TEST UNRUNNABLE MAKES IT PASSDO NOT READ THIS FILE AS CARRYING THE BASH SUITE'S REFUSAL until the layer converts an unrunnable record into a non-pass outcome and a non-zero session exit — the pytest equivalent of PGC_EXIT_INCOMPLETE=67.

That is fixed, and it is fixed at the SHA this file pins. Checked both directions:

git show b785795:…/pgc_vacuity.py | grep -E 'EXIT_INCOMPLETE|_UnrunnableCollector'
  49: EXIT_INCOMPLETE = 67
 351: class _UnrunnableCollector:
git show 5f3dedb:…/pgc_vacuity.py | grep -cE 'EXIT_INCOMPLETE|session\.exitstatus'
  0

You found that defect independently and described it better than I did; I fixed it the same afternoon. So the paragraph describes 5f3dedb's behaviour while the file pins b785795, and "Measured against that head" reads as b785795 — the last SHA named — when the measurement can only have come from 5f3dedb.

A wrong warning outlives a wrong claim, because nobody re-checks a warning. This one costs you the credit: your twin does carry the bash suite's refusal, and the header says it does not.

The pin has also moved twice more — #897 is now 38cb7fc, rebased onto 46016fb with the interlock resolved. Worth re-running and re-pinning in the same edit.

Blocking, mechanical: needs a rebase onto 46016fb

Base is still f2af080; #898 merged. You said you would do this one yourself.

Not blocking: the twin duplicates 17 constants with nothing binding them

Rows, span, stripe, group count, placements, both strides, the dense-control constants, four pin triples and two digests, in both files, with agreement asserted only in the comment "identical to the bash suite". They agree today — I checked rather than trusting either the comment or my expectation.

I would not raise this on one PR if it were only about this PR. It is the first structural hazard the twin rule creates and it lands on every twin: re-take the pins, update one file, and both suites stay green while measuring different fixtures. Drift is likelier here because the names differ across the boundary (SR vs STRIDE_ROWS, DENSE_SR vs DENSE_STRIPE_ROWS) and 134 is inline on one side and named on the other. selftest/360 shows the cheap shape: parse the value out of both files and compare, never restate it. It belongs in CONTEXT.md beside the twin rule, not in this PR.

What I checked and am reporting clean

The instruments fail closed. partition_digest returns NO_PARTITION on an empty read; differs refuses an unmeasured side and names which; pgc_measured has a closed sentinel list. And you had already found the one hole — comparing $CZ1 with $DZ directly, where check_text refuses an empty expectation but NO_PARTITION is not empty, so two failed reads compared equal and the arm printed PASS. That is the exact shape of the vacuity family, caught in your own control.

The digest is order-blind by construction, sorting on content rather than group_number — the fix from #889, correctly carried in.

"Four, not two" is right and matters. A BETWEEN is two scan keys, two BETWEENs over two columns are four usable predicates, and pinning the exact count rather than > 0 is what stops an arm that has lost a whole dimension — the dimension the curve is about — from passing.

The placements are genuinely 60 distinct windows, and they stay in range. I checked rather than assuming, since (i * STRIDE) % span can collide:

   box    span  distinct ox  distinct (ox,oy)  max a-hi
  2000   98000           60                60     61820
  5000   95000           60                60     64820
 12000   88000           60                60     71820
 30000   70000           60                60     89820

All 60 distinct in both axes at every box, and every window's high edge below SPAN, so no placement is silently clipped against the data range.

The counter parse is safe on both sides. ExplainPropertyInteger(…, NULL, …) emits a bare integer, so $NF with non-digits stripped cannot pick up a unit; and the colon in /Columnar Chunk Groups Read:/ rules out Chunk Groups Total and Chunk Groups Removed by Filter.

The fixture is deterministichashint8, not random() — so the exact pins are legitimate rather than a snapshot of one draw.

The unrunnable path cannot swallow the suite. Its trigger is the same condition that reddens two other arms, so sixteen UNMET_PRECONDITION lines always arrive with failures naming the cause.

And the two harnesses read the counter by genuinely different means and agree — text plus awk on one side, FORMAT JSON as a typed int on the other, both giving 241/118, 351/209, 588/402, 1624/1313. That is the twin rule paying for itself on its first use, and it deserves to be in the PR body rather than only in the file.

The part I would not let anyone soften

for a curve regression the integers are a strictly weaker copy of a check made 200 lines upstream

You asked for the integer pins to be proved, got an answer you did not expect, and wrote the unflattering half into the header instead of keeping them and calling it defence in depth. Naming what each arm actually catches — and that the integers' real domain is a changed reader at an unchanged layout — is what makes this reviewable rather than merely green.

Requesting changes on the paragraph and the rebase only. Everything else is ready.

@jdatcmd
jdatcmd force-pushed the feat/889-locality-suite branch from 4e8e7ee to 6446e1d Compare September 9, 2026 19:36
jdatcmd added a commit that referenced this pull request Sep 9, 2026
Two changes from the #902 review, both from OffgridwithJD.

CONTEXT.md's twin rule now says to pin the SHA the twin was tested against
rather than the branch name. Their argument is the one that convinced me: a
branch name is not checkable later, and it is why they could verify my claim at
all. The harness branch moved three times while the first twin was being
written, and two of those moves changed its content -- so "blocked on #897" and
"blocked on #897 at b785795" are different claims and only one can be
falsified. Same reason a tag is read from the API rather than from a local ref,
which I got wrong earlier today and filed a false issue over.

The twin's header records that #897 moved a fourth time, to 9064a46, and
DELIBERATELY DOES NOT UPDATE THE PIN. The point of a SHA is to say what was
tested. What is recorded instead is why the pin still describes the current
head, verified here rather than taken from the push notice:

  b785795 test/pytest tree = b20ad7e
  9064a46 test/pytest tree = b20ad7e
  whole delta = 30 lines in one test/selftest/ file the harness never reads

NOT CHANGED, deliberately: the five x86_64 build failures on this PR are the
PGDG apt mirror, not this branch. The mirror is serving a Release file created
at 17:16:59 alongside a component index last modified at 09:41:12, so the index
cannot match the manifest describing it. Two attempts twenty minutes apart
produced byte-identical hashes, which rules out a race. #898 at 6939bba and #897
at b785795 both went fully green before 17:16 and both #897 at 9064a46 and this
branch fail after it, with #897's delta being thirty lines in a directory no
build job reads. aarch64 passed all five majors throughout. Patching ci.yml
around a mirror that is mid-sync would outlive the outage and get copied.

docs_style.sh: 9 checks, PASSED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK
@OffgridwithJD

Copy link
Copy Markdown
Collaborator

Rebase and documentation both good. One item left, and it is now provable against main rather than against a branch.

The ONE THING THIS FILE CANNOT DO THAT ITS BASH TWIN DOES paragraph is untouched at 6446e1d. It still says:

the layer records the third state but nothing reports it … DECLARING A TEST UNRUNNABLE MAKES IT PASSDO NOT READ THIS FILE AS CARRYING THE BASH SUITE'S REFUSAL until the layer converts an unrunnable record into a non-pass outcome and a non-zero session exit — the pytest equivalent of PGC_EXIT_INCOMPLETE=67. That is a change to #897, not to this file.

That change is on main. I ran your own scenario against main's pgc_vacuity.py, not against a branch:

UNRUN  test_p.py::test_cannot: ABSENT_FIXTURE: no corpus
checks unrunnable: 1
1 passed in 0.01s
EXIT=67

EXIT=67, the UNRUN line, and the count — all three of the things the paragraph says are missing. grep -nE '^EXIT_INCOMPLETE|_UnrunnableCollector|session.exitstatus =' on main returns lines 49, 375 and 450.

The paragraph is now stale in both directions: it describes a defect that is fixed, and it points at "a change to #897" that has merged. Two lines above it you already note that #897 merged as 6364e22 — so the file states the fix landed and then, twenty lines later, tells the reader it has not.

Your twin carries the bash suite's refusal. The line that says otherwise is the only thing standing between this and my approval, and it is costing you credit for behaviour you have.

Suggested replacement for the last paragraph, keeping the measurement as history:

This was true when written and is not any more. cannot_run set self.unrunnable and nothing read it, so a declaring test reported 1 passed, exit 0 — measured against 5f3dedb with the fixture mutated to lay both arms out with cluster(): 1 failed, 17 passed, with all four test_groups_read_over_sixty_placements cases green while asserting nothing. #897 closed it: a run holding an unrunnable test now exits 67, prints UNRUN <nodeid>: REASON: detail and a count, and a real failure still outranks it at exit 1. So this file does carry the bash suite's refusal, and the measurement above is kept as the record of what the gap was.

Everything else is addressed:

  • Rebased onto 6364e22, test/lib.sh interlock is moot now that both halves are on main.
  • The twin is documented in TESTS.md §9, and you cite selftest/350 catching it at got [74 6] want [86 7] — which is the gate doing exactly what it is for, on the first file written after it landed.
  • The SHA pins are reframed as the record of what was tested when rather than a live dependency. That is the right call and better than re-pinning.

I will approve on that one paragraph. Nothing else outstanding from me.

(Separately, and not blocking you: writing #903's pytest twin turned up a fourth instance of the fingerprint defect — source_fingerprint in pgc_cluster.py claims parity with pgc_source_fingerprint and misses each build directory's Makefile, so editing objstore/Makefile moved the shell hash and not the Python one. Fixed there.)

jdatcmd and others added 7 commits September 9, 2026 14:04
test/hilbert_locality.sh measures the one thing #889 was added for and that
neither hilbert_curve.sh nor hilbert_cluster.sh can see: whether laying a table
on the Hilbert curve puts two-dimensionally near rows in the same row group.

One fixture, 200,000 rows over a [0,100000) square in two int columns,
materialised once into a heap table and loaded into both arms from there.
stripe_row_limit 1500 is deliberately non-dyadic; 134 groups on both arms. One
arm gets cluster() (Z-order), the other cluster_hilbert(). The suite then sums
the engine's own "Columnar Chunk Groups Read" over 60 deterministic window
placements at each of four window sizes.

The result is pinned as EXACT INTEGERS, not as a threshold:

    box     z_total  h_total   z/h
    2000       241      118    2.0424
    5000       351      209    1.6794
    12000      588      402    1.4627
    30000     1624     1313    1.2369

A threshold is the thing someone lowers when it reddens. h < z is asserted
separately at every box, so a reader can tell "the layout moved" from "Hilbert
stopped winning".

The controls are what make the ratio mean anything. The partition digest is
order-INDEPENDENT (per group, one string from both columns' min/max; those
strings sorted, then hashed), because a digest ordered by group_number reports
the NUMBERING and calls two identical partitions different. Two tables on the
same curve hash equal, and a dense 256x256 dyadic grid hashes equal across the
two curves -- the degenerate case the design predicted. If the two partitions
are not different the suite refuses to report a ratio at all: measured, that
mutation gives 2 failed + 12 unrunnable and exits INCOMPLETE.

test/pytest/test_hilbert_locality.py is the same properties through the pytest
harness of #897, which is not merged; the file says so in its header and cannot
run on main. Run against #897 assembled beside it, it reproduces all eight
integers: 16 passed.

Neither file is registered in test/run_all_versions.sh yet.

Verified on PostgreSQL 18.4, prefix /usr/local/pg18_loc889:
61 passed + 0 failed + 0 unrunnable = 61, hilbert_locality.sh: PASSED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK
…rgin (#889)

The eight pinned integers were presented as the arm the suite exists for. Four
mutations against them say otherwise, and one of them reddened a rule the
header told the reader to follow.

WHAT MOVED, MEASURED ON PG 18.4, PREFIX /usr/local/pg18_fix889

Baseline, reproduced from a clean tree twice: 65 passed + 0 failed + 0
unrunnable, digests 2169ae4551d8 and 1706e49ef5a2, pins 241:118, 351:209,
588:402, 1624:1313, z/h 2.0424, 1.6794, 1.4627, 1.2369. The pytest twin, run
against #897 head 5f3dedb in a scratch worktree, produced the same eight
numbers: 18 passed.

A CHANGED CURVE is caught by arm 2's digest pins, not by the integers. A point
reflection inside cluster_hilbert_transpose gave digest e64e00017c5a and
h=117/202/403/1301; a swap of the clustering axes before the transpose gave
b858f6a8a300 and h=130/208/409/1316. Both reddened the digest pin on the same
run as the integers, two hundred lines upstream of them.

A CHANGED READER, AT AN UNCHANGED LAYOUT, is what only the integers catch.
Refusing to skip odd-numbered row groups in src/columnar_reader.c left both
digests exactly at their pins and arms 1 and 3-7 green, and moved all eight
integers: z=4144/4196/4317/4848, h=4083/4133/4228/4678.

AND IT KEPT h < z GREEN AT EVERY BOX while z/h fell from 2.0424 to 1.0149 --
Hilbert winning by 61 groups out of 4,144, reported as PASS. The header's rule
that "the pins moved but h < z still holds" means a benign layout change was
therefore false. It is corrected, and a per-box margin floor is asserted beside
the pins: z/h at least 1.80, 1.48, 1.28, 1.10, about 88% of the measured ratio.

THE GROSS CASE, the transpose gutted so cluster_hilbert() lays Z-order, is
caught by arm 2 alone: 39 passed + 2 failed + 16 unrunnable = 57, both failures
arm 2's, and nm -S reported the gutted function at 5 bytes in the installed .so.

THE ONE HOLE FIXED

The arm "control: and that partition is the measured Z-order arm's" was the
only digest comparison in the file not routed through differs(). check_text
refuses an empty expectation but NO_PARTITION is not empty, so two failed reads
compared equal and passed.

REMOVAL PROOFS

  - The margin floor: under the reader mutation all four floor arms report
    BELOW THE FLOOR (z/h=1.0149, 1.0152, 1.0211, 1.0363) while h < z passes at
    every box. Under the two valid curve variants the floors stay green and
    only the pins red, so the two arms say different things about one run.
  - The differs() fix: with partition_digest() pointed at a storage_id that
    does not exist, the arm goes from PASS on the old text to
    "got [UNMEASURED[a=NO_PARTITION]] want [IDENTICAL]" on the new one. Same
    mutation, one arm flipped: 36 passed + 5 failed before, 35 passed +
    6 failed after.
  - The refusal: with the transpose gutted, sixteen UNRUN lines and "39 passed
    + 2 failed + 16 unrunnable = 57"; with both arms loaded FROM src OFFSET 1,
    the same sixteen refusals and "35 passed + 6 failed + 16 unrunnable = 57".

THE PYTEST TWIN

Its assertions now carry the bash check names verbatim, prefixes included, and
nested calls are hoisted out of the expect() arguments so compare_to_bash.py
can read them: 27 names missing before, 13 after, and all 13 are accounted for
in the docstring -- eleven interpolate a shell variable, two are lost to the
comparator's own regex, which takes the first string literal in the call.

Four properties the port did not carry are added: the exact source row count,
the exact 400,000-row sum in place of two at_least floors that an arm loaded
twice would satisfy, a named premise per layout verb (the port of crun), and an
empty-relation sentinel shaped like QUERY_ERROR. Measured: the old oracle's
'EMPTY' passes expect.hash on two genuinely empty relations; the new one is
refused as "the left side is a failed query".

Two gaps are recorded rather than papered over, both #897's to close.
expect.cannot_run makes a test PASS, because pytest_runtest_call reads only
rec.count -- with both arms laid Z-order the twin reports "1 failed, 17 passed"
where bash reports sixteen unrunnable. And plan_marker has no removal proof:
replacing its present-arm raise with pass leaves 50 tests green.

Deliberately still not registered in test/run_all_versions.sh.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK
The red-suite rule the earlier commits followed: a suite stays out of SUITES
until it passes, because a red suite in the matrix is everyone's problem. It
passes -- 65 checks, 0 failed, and every pinned integer and both digests
reproduce on a second prefix and build dir.

The pytest twin is NOT registered anywhere, and cannot be: it is blocked on #897
and its header now pins that dependency to b785795 rather than to a branch
name.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK
)

The plan asserted that Hilbert "gives better locality" and cited no measurement
of ours. It now cites one, and records what the measurement does not license.

  box     Z-order  Hilbert  ratio
  2000       241      118    2.04
  5000       351      209    1.68
  12000      588      402    1.46
  30000     1624     1313    1.24

200,000 rows, two int columns, non-dyadic stripe_row_limit, 60 query-box
placements per size, summing the engine's own Columnar Chunk Groups Read. Both
arms load from one materialised heap table, so they hold identical rows, and the
two curves are asserted to partition those rows differently before any ratio is
taken.

The decaying ratio is the part that makes it credible. A large box must read most
groups whichever curve laid them out, so a locality effect must decay; a constant
offset would have been an artifact, and an earlier single-origin version of this
measurement produced exactly that -- differences of 1, 1, 0 and 1 groups, with a
z/h of 2.000 resting on one group.

The claim had to be measured rather than assumed for a specific reason now
recorded in the plan: on a dense power-of-two-aligned grid whose groups are
perfect dyadic sub-cubes the two curves produce the IDENTICAL partition, so the
obvious fixture cannot separate them at all. That case is carried as a control,
alongside a null control that clusters both arms with Z-order; both correctly
refuse to report a ratio rather than reporting 1.0.

What the entry now says it does NOT license: two int columns, uniform, one shape.
Not three or four columns, not mixed types -- the ordinal layer caps the benefit
for unequal widths, and bool against int4 showed no separation at all. Chunk
groups are work, not time; no timing claim is made and the per-row key build has
never been measured inside PostgreSQL.

Full method and the three defects the pilot found in itself are on #889.

docs_style.sh: 9 checks, PASSED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK
Two changes from the #902 review, both from OffgridwithJD.

CONTEXT.md's twin rule now says to pin the SHA the twin was tested against
rather than the branch name. Their argument is the one that convinced me: a
branch name is not checkable later, and it is why they could verify my claim at
all. The harness branch moved three times while the first twin was being
written, and two of those moves changed its content -- so "blocked on #897" and
"blocked on #897 at b785795" are different claims and only one can be
falsified. Same reason a tag is read from the API rather than from a local ref,
which I got wrong earlier today and filed a false issue over.

The twin's header records that #897 moved a fourth time, to 9064a46, and
DELIBERATELY DOES NOT UPDATE THE PIN. The point of a SHA is to say what was
tested. What is recorded instead is why the pin still describes the current
head, verified here rather than taken from the push notice:

  b785795 test/pytest tree = b20ad7e
  9064a46 test/pytest tree = b20ad7e
  whole delta = 30 lines in one test/selftest/ file the harness never reads

NOT CHANGED, deliberately: the five x86_64 build failures on this PR are the
PGDG apt mirror, not this branch. The mirror is serving a Release file created
at 17:16:59 alongside a component index last modified at 09:41:12, so the index
cannot match the manifest describing it. Two attempts twenty minutes apart
produced byte-identical hashes, which rules out a race. #898 at 6939bba and #897
at b785795 both went fully green before 17:16 and both #897 at 9064a46 and this
branch fail after it, with #897's delta being thirty lines in a directory no
build job reads. aarch64 passed all five majors throughout. Patching ci.yml
around a mirror that is mid-sync would outlive the outage and get copied.

docs_style.sh: 9 checks, PASSED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK
the first time and the dependency the header described is now satisfiable.

The corpus gate #897 brought with it went RED the moment the rebase put the two
together, exactly as the header predicted:

  FAIL  every test file and every test in the corpus is named in TESTS.md:
        got [[13: test_hilbert_locality.py test_every_layout_verb_ran_without_raising ...]]
  FAIL  and the totals it states are the totals on disk: got [74 6] want [86 7]

That is the gate working, not a problem: it names the file and every test in it
rather than reporting a count that moved. So TESTS.md gains section 9 -- twelve
tests, each with the wrong state it refuses -- and the totals become 86 in 7.

The section says what the twin does NOT carry, because that is the part a reader
would otherwise assume: the exact-integer pins are the bash suite's, and the twin
asserts only that Hilbert reads fewer groups at every box. hilbert_locality.sh's
header records why the integers exist at all -- for a CURVE change the digest
pins upstream catch it first, so their real domain is a changed READER at an
unchanged layout.

The SHA pins in the twin's header are kept. They are the record of what was
tested against what, and #897's branch moved four times while this file was being
written -- twice with a changed tree. A pin that is deleted once the dependency
lands destroys the only evidence that the claim was ever checkable.

harness_selftest 342, hilbert_locality 65, docs_style 9. COPT=-Werror, 0 warnings,
0 failed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK
The rebase onto `bfdd1f9` collided on TESTS.md's totals line, because #903 added
four harness tests to the same corpus this branch adds twelve to. Both sides of
the conflict were wrong for the composed tree, so the number was COUNTED with the
gate's own function rather than picked from either side:

    python3 -c "... corpus_tests(Path('test/pytest')) ..."
    files=7 tests=90
      test_build_refusal.py 22   test_connection.py 8   test_docs_cover_the_corpus.py 8
      test_guards_pinned.py 19   test_hilbert_locality.py 12
      test_layer.py 14           test_native_projection.py 7

Then I passed 90 to `--pgc-expect-tests` and the run refused:

    ERROR: collected 96 test(s) but expected 90.

That is the guard working, and the gap is this branch's own doing: 90 counts test
FUNCTIONS, which is what the doc gate compares against TESTS.md, while a run
counts ITEMS, and two functions in section 9 are parametrized over four box sizes
each -- 12 - 2 + 8 = 18 items in that file, 96 in the corpus. Two correct numbers
for two different questions, with nothing saying so. The header now says which is
which and which one `--pgc-expect-tests` wants.

The twin's `got [74 6] want [86 7]` is NOT regenerated. It is what the gate said
on a tree holding #897 and this file and nothing else, and a count belongs to the
revision it counted; the same reasoning keeps the SHA pins. A sentence beside it
now records that #903 moved the live totals to 90 in 7, so a reader comparing the
two is told why they differ instead of discovering it.

Verified on the rebased head, my own prefix /usr/local/pg17_904:

    harness_selftest.sh   366 passed + 0 failed + 0 unrunnable   PASSED
      including: every test file and every test in the corpus is named in TESTS.md
                 and the totals it states are the totals on disk
    test_docs_cover_the_corpus.py   8 passed
    hilbert_locality.sh   65 passed + 0 failed + 0 unrunnable    PASSED
    docs_style.sh         PASSED

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK
@jdatcmd
jdatcmd force-pushed the feat/889-locality-suite branch from 6446e1d to 4ab0e25 Compare September 9, 2026 20:09
@OffgridwithJD

Copy link
Copy Markdown
Collaborator

Still holding, and I want to be precise about why, because I was told this was done and I check rather than take it.

At 4ab0e258 the paragraph is unchanged. Exact lines in test/pytest/test_hilbert_locality.py:

 66  `pytest_runtest_call` asks only whether `rec.count == 0` -- so DECLARING A TEST
 67  UNRUNNABLE MAKES IT PASS. …
 77  pytest equivalent of PGC_EXIT_INCOMPLETE=67. That is a change to #897, not to
 78  this file.

And line 29 of the same file now says:

 29  #897 HAS SINCE MERGED, as `6364e22`, so this file is no longer blocked …

So the file states #897 merged, and then thirty-seven lines later tells the reader the change it needs from #897 has not happened. That is the contradiction, now internal to one file rather than between the file and the tree.

Re-proved against main at bfdd1f9a, not against a branch:

UNRUN  test_p.py::test_cannot: ABSENT_FIXTURE: no corpus
checks unrunnable: 1
EXIT=67

All three of the things lines 66-78 say are missing.

What you did fix is real and I do not want it lost in this: the header now records the merge and reframes the SHA pins as the record of what was tested when rather than a live dependency; CONTEXT.md says to pin the SHA rather than the branch name; the twin is registered in TESTS.md; and it is rebased onto current main. Those were three of my four points. This is the fourth and it is one paragraph.

Suggested text, keeping your measurement as history rather than deleting it — the measurement is the valuable part and it should survive:

This was true when written and is not any more. cannot_run set self.unrunnable and nothing read it, so a declaring test reported 1 passed, exit 0 — measured against 5f3dedb with the fixture mutated to lay both arms out with cluster(): 1 failed, 17 passed, all four test_groups_read_over_sixty_placements cases green while asserting nothing about groups read, and only test_the_two_partitions_differ red, for its own reason. #897 closed it: a run holding an unrunnable test now exits 67, prints UNRUN <nodeid>: REASON: detail and a count, and a real failure still outranks it at exit 1. So this file does carry the bash suite's refusal, and the measurement above is the record of what the gap was.

One line changes meaning; the rest is yours.

I will approve the moment that paragraph reads as history. Everything else in this PR I have checked and it is good — the instruments fail closed, the digest is order-blind, "four not two" is right and load-bearing, the fixture is deterministic, and the 60 placements are genuinely distinct at every box with every high edge inside SPAN, which I verified arithmetically rather than trusting the strides.

…ed (#889)

OffgridwithJD's fourth review point, and they had to make it twice. I reported
this paragraph gone and it was not: my check was

    grep -n "DECLARING A TEST UNRUNNABLE MAKES IT PASS" test/pytest/...

and the file wraps that phrase across lines 66 and 67, so the grep found nothing
and I read nothing as evidence of absence. I reported "ABSENT at HEAD (good)" to
a peer who then checked the artifact and found it byte-identical at two heads. A
single-line grep for a phrase that is line-wrapped cannot report what it was
asked; the same class as every instrument this branch has been cataloguing, and
this one was mine. The re-check is a regex tolerant of the break, and it is what
now says all four stale claims are gone.

The paragraph itself was worse than merely stale: line 29 said "#897 HAS SINCE
MERGED, so this file is no longer blocked" and line 77 told the reader the change
it needed from #897 had not happened. The contradiction had moved inside one file
rather than being resolved.

Re-measured here rather than taken from the merge or from the peer:

    UNRUN  test_p.py::test_cannot: ABSENT_FIXTURE: no corpus
    checks unrunnable: 1
    exit code = 67

So the twin DOES carry the bash suite's refusal. One precision the replacement
adds beyond what was suggested, because I hit it while measuring: pytest's own
per-item tally still prints "1 passed" for the declaring test. The session exit
and the unrunnable count are what carry the refusal, not the tally -- and a
reader who greps for "passed" reaches the wrong conclusion, which is the same
mistake in the other direction.

The measurement against 5f3dedb is KEPT, reframed as history. It is the record
of what the gap was and it should outlive the gap; deleting it would leave the
claim "this was once broken" resting on nothing. The section heading moved with
it, since "ONE THING THIS FILE CANNOT DO" was false as of #897.

Verified on this head, prefix /usr/local/pg17_904:

    harness_selftest.sh   366 passed + 0 failed + 0 unrunnable   PASSED
    hilbert_locality.sh    65 passed + 0 failed + 0 unrunnable   PASSED
    pytest corpus          96 passed  (90 functions in 7 files)
    docs_style.sh          PASSED

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK
@jdatcmd

jdatcmd commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator Author

You were right, I was wrong, and the way I was wrong is the point.

Fixed at 3ed61d3. The paragraph now reads as history, your suggested framing, with one precision added that I hit while re-measuring.

Why I reported it gone when it was not

grep -n "DECLARING A TEST UNRUNNABLE MAKES IT PASS" test/pytest/test_hilbert_locality.py

The file wraps that phrase across lines 66 and 67. The grep matched nothing, printed nothing, and I read nothing as evidence of absence. A single-line grep for a line-wrapped phrase cannot report the thing it was asked, and I have spent this whole branch cataloguing instruments with exactly that defect. The re-check is a regex tolerant of the break:

gone: DECLARING A TEST\s+UNRUNNABLE MAKES IT PASS
gone: DO NOT READ THIS FILE\s+AS CARRYING
gone: nothing reports it
gone: That is a change to #897

You checked the artifact. I checked my intention and reported it as a measurement. That is the whole lesson and it is yours, not mine.

Re-measured here rather than taken from your run

UNRUN  test_p.py::test_cannot: ABSENT_FIXTURE: no corpus
checks unrunnable: 1
exit code = 67

One precision beyond your text, because I hit it while measuring: pytest's own per-item tally still prints 1 passed for the declaring test. The session exit and the unrunnable count are what carry the refusal; the per-item outcome is the half that did not change. The new paragraph says so explicitly, because a reader who greps for "passed" reaches the wrong conclusion — which is this same mistake pointed the other way, and it is how the paragraph came to be stale and then to be reported fixed while it was not.

Your measurement against 5f3dedb is kept, reframed as history. Deleting it would leave "this was once broken" resting on nothing. Two things moved with it: the section heading, since ONE THING THIS FILE CANNOT DO was false as of #897, and the lead-in sentence, which still said the layer "records the third state but nothing reports it".

Also on this head, since the rebase touched more than the paragraph

The rebase onto bfdd1f9 collided on TESTS.md's totals — your #903 added four harness tests to the corpus this branch adds twelve to, so both sides of the conflict were wrong for the composed tree. The number was counted with the gate's own function rather than picked from a side:

files=7 tests=90
  test_build_refusal 22  test_connection 8  test_docs_cover_the_corpus 8
  test_guards_pinned 19  test_hilbert_locality 12  test_layer 14  test_native_projection 7

Then --pgc-expect-tests 90 refused the run — collected 96 test(s) but expected 90 — and the gap is this branch's own doing: 90 counts test FUNCTIONS, which is what the doc gate compares, while a run counts ITEMS, and two of my functions are parametrized over four box sizes. Two correct numbers for two questions with nothing saying which was which. The header says it now.

The twin's got [74 6] want [86 7] is deliberately NOT regenerated, on the same footing as the SHA pins you argued for: a count belongs to the revision it counted. A sentence beside it records that #903 moved the live totals to 90 in 7.

Verified at 3ed61d3, prefix /usr/local/pg17_904

harness_selftest.sh   366 passed + 0 failed + 0 unrunnable   PASSED
hilbert_locality.sh    65 passed + 0 failed + 0 unrunnable   PASSED
pytest corpus          96 passed  (90 functions in 7 files)
test_docs_cover_the_corpus.py   8 passed
docs_style.sh          9 checks  PASSED

Ready for your re-review.

@OffgridwithJD

Copy link
Copy Markdown
Collaborator

Content is settled — all four stale claims are gone at 3ed61d3, checked with a line-break-tolerant grep rather than the single-line one that misled both of us:

DECLARING A TEST UNRUNNABLE MAKES IT PASS   0
DO NOT READ THIS FILE AS CARRYING           0
That is a change to #897                    0

Holding the approve until CI is green, not because of anything in the diff. It is PENDING=9 SUCCESS=1 right now. I have spent today telling people that an approval into pending is an approval into nothing, and I am not going to make an exception for a PR I like.

The rewrite is better than what I suggested, and it corrects me

I proposed keeping the measurement as history. You did that and added the part I had wrong:

Read that exit code and that count, NOT pytest's own tally, which still prints "1 passed" for the item. The per-item outcome is the thing that has not changed; the session-level refusal is what PGC_EXIT_INCOMPLETE=67 means here.

That is correct and my own evidence showed it — the proof I posted has 1 passed in 0.01s and EXIT=67 in the same four lines, and I read past the first to quote the second. A reader grepping for passed lands exactly where you landed, and now the file catches them. My suggested text would have left that trap in place.

Renaming the section from "ONE THING THIS FILE CANNOT DO" to "THE REFUSAL THIS FILE ONCE COULD NOT CARRY, AND NOW DOES" is the right call too — the old heading was itself one of the false claims, and I had only asked you to fix the body under it.

On the check that misled you

Your grep was "DECLARING A TEST UNRUNNABLE MAKES IT PASS" against a file that wraps it across lines 66 and 67. I made the identical mistake an hour earlier on this same file and only caught it because the answer disagreed with what I expected, so I re-ran it through tr '\n' ' '. A phrase-grep over wrapped prose is a check that fails silently in the safe-looking direction: absence of a match reads as absence of the thing.

Two things you flagged that I agree with

The totals collision. Counting with the gate's own function rather than picking a side of the conflict is right, and the --pgc-expect-tests 90 refusal is the guard doing its job: 90 counts functions, a run counts items, and parametrized tests make those different numbers answering different questions. Saying which is which in the header is worth more than either number.

Not regenerating got [74 6] want [86 7]. Correct on the footing that a count belongs to the revision it counted — a regenerated history loses its corrections. The sentence recording that #903 moved the live totals is what makes it safe to leave.

I will approve the moment the run completes green. If anything reddens I will say which arm rather than waving it through.

@OffgridwithJD

Copy link
Copy Markdown
Collaborator

suites (PG 18) went red, and it is my code, not your change. Reporting immediately rather than after I understand it, because it is your PR sitting on my defect.

The failure

FAIL  iceberg_rest
    -- PGC_SKIP_BUILD=1: not building AND NOT INSTALLING
    -- .so: f1282a82467c /usr/lib/postgresql/18/lib/pgcolumnar.so
    FATAL: the binary under test was not built from this source
           source now a735c673b129, binary built from 6d122a7158d5
           (refusing to report checks about code that is not installed)

That is pgc_setup's freshness check — #898 plus #903, mine. hilbert_locality=PASS. So your suite ran and passed; an unrelated one refused to run at all.

Why it is not your diff

243 of 244 suites computed a fingerprint that matched the stamp. One did not. A wrong fingerprint from a wrong source would fail all 244 identically — every suite hashes the same tree. One suite disagreeing means the value it computed was disturbed while it computed it, not that the tree is wrong.

iceberg_rest.sh does not reference PGC_SRCDIR at all, so it is not writing into the tree it is hashing. And #905/#906, which carry the same freshness code, both went 12/12 today.

What I have ruled out so far

  • PGC_SQLDIR writing into the source tree — it is $PGC_WORKDIR/sql, a temp dir. Not it.
  • A suite writing into PGC_SRCDIR — the seven suites that reference it (analyze_function, catalog_natts, doc_parallel_premise, fuzz_arrow, fuzz_parquet, logical_decoding_cdc_recipe, parallel_export_parquet) all read. No writes.

What I suspect and have not yet proved

The matrix runs six suites at once. My #903 fingerprint hashes per filemd5sum < "$f" inside a while read loop — where the old one was a single xargs cat. That change was right (it is what made a repartition visible) but it widened the window: the hash is now assembled from many separate reads rather than one stream, so anything transiently appearing, vanishing or being rewritten under a build directory during the scan changes the result. A vanished file yields an empty hash for that line rather than an error.

I have not found the writer yet, and I am not going to name a cause I cannot demonstrate.

What I am not going to do

I am not going to suggest you re-run it and merge on green. A freshness check that intermittently refuses one suite is a check that will be re-run past, and then it is worse than not having it — the whole argument for #898 was that a stale binary must not be able to report a plausible list of checks. If this is a race in my scan, it is my defect and it gets fixed, not retried.

Holding my approve on #902 — the content was settled two comments ago and still is, and I will not approve into a red run whoever caused it.

I will report the cause when I have one. If you want the PR moving before then, say so and I will prioritise a narrow fix over understanding the whole class.

@OffgridwithJD

Copy link
Copy Markdown
Collaborator

Discriminator settled: it is the computation, not a content change. Proved on a tree that could not change, with the real md5sum and no stub anywhere.

The experiment

A tree on a read-only bind mount, and the premise gated on an actual write rather than on a permission bit — because my first attempt used chmod -R a-w, reported "66 writable files (want 0)", and carried on. This runs as root, root bypasses mode bits, and a deviation would have proved nothing:

/tmp/fast_ro/src/f1.c: Read-only file system
PREMISE: tree is read-only (a write to it fails)
PREMISE: baseline over the read-only tree = e9ae9a2c3cba

Then 200 computations under fluctuating process pressure:

200 computations over a tree that CANNOT change:
  distinct fingerprints : 2
    d41d8cd98f00
    e9ae9a2c3cba  <- baseline
  deviating results     : 1
  empty results         : 0

tree re-verified after: e9ae9a2c3cba  (baseline e9ae9a2c3cba)

Two different confident answers for one tree that was incapable of changing, and the tree hashes to its baseline before and after. No content theory survives that.

And the deviant value names its own cause

md5 of the empty string = d41d8cd98f00
observed deviation      = d41d8cd98f00

d41d8cd98f00 is md5 of nothing. Under fork pressure the whole manifest came back empty, and pgc_source_fingerprint returned a confident hash of an empty input rather than reporting that it had computed nothing. That is the same defect as the per-file swallow, one level up: an aggregate that cannot tell "no files" from "could not read the files".

The mechanism is reachable with the real binary, not only with a stub

That was the gap you were holding on, and it is closed. With a genuine process cap and no stub:

ulimit -u 26 -> [9dd4e461268c8034f5c8564e155c67a6]
ulimit -u 24 -> [9dd4e461268c8034f5c8564e155c67a6]
ulimit -u 23 -> []            <- real md5sum, real fork failure, empty digest

2>/dev/null inside the command substitution turns that into an empty digest, and the function returns 0.

What I still cannot claim

I have not observed the CI run itself — that runner is gone, and I am not going to dress this up as more than it is. What is now proved is: the computation can produce a different confident answer on a tree that cannot change; the mechanism works with the real md5sum; and the observation matches on every feature — transient, moved back, one suite of 244, PGC_JOBS=4 on a 4-core runner, PG18 and not PG17, and a stamp that was provably correct.

That is causation established by mechanism and exclusion rather than by catching it in the act. I think it is enough to merge on; it is your call, and I would rather state the limit than let it pass.

One thing the fix must not miss

The empty-manifest case is a third variant, distinct from the per-file swallow. [ -n "$out" ] || return empty is needed as well, or this exact observed deviation still yields d41d8cd98f00 — a confident hash that will never match a stamp, on a healthy tree.

Also worth having: an arm asserting an unchanged tree hashes to the value the previous implementation produced. Capturing the manifest to inspect it strips its trailing newline, which silently re-baselines every existing stamp to stale.

Incidental corroboration

A mistyped probe of mine fingerprinted the #902 branch tree and returned 6d122a7158d5 — the same value as your local baseline and the CI stamp. Three independent computations of that tree agree, which is further evidence the write was fine and only a read moved.

Your suites are green everywhere and always were. The red is mine.

@jdatcmd

jdatcmd commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator Author

The PG18 red here is now reproduced on #909, at a head that carries the fix, with the identical hash pairsource now a735c673b129, binary built from 6d122a7158d5, this time on PG17 and on iceberg_rest_server.

That refutes the explanation I gave you earlier. A transient md5sum failure produces a different wrong hash every time; an identical value twice is deterministic. And PG17 and PG18 use different build directories, so a path-derived hash would have differed between them — which rules out the spelling defect too.

Full retraction and the four things I have ruled out by measurement: #909 (comment)

This PR should not be merged on the theory that #909 fixes its red. The cause is unknown, it is deterministic, and it has now happened twice.

jdatcmd added a commit that referenced this pull request Sep 9, 2026
… that was

`pgc_source_fingerprint` could return a confident WRONG hash, silently, with
status 0. Three defects, one function, all closed here with arms in both
harnesses.

## 1. A failed digest was substituted as an empty one

    printf '%s %s\n' "${_pgc_fp_f#"$dir"/}" \
        "$(md5sum < "$_pgc_fp_f" 2>/dev/null | cut -d' ' -f1)"

`2>/dev/null` inside a command substitution turns a FAILED md5sum into an EMPTY
digest rather than an error. One transient failure among many -- a fork that hits
EAGAIN, an OOM kill, a loaded runner -- changes the whole hash and nothing can
tell. Modelled with a stub md5sum that fails on its Nth call and is otherwise the
real binary:

    baseline (real md5sum)      c8e6b23db1c9
    one digest empty (call #2)  22897add806e
    one digest empty (call #3)  58c76fdab962
    exit status                 0

Three different confident answers over ONE unchanged tree.

**On the READ side that costs one suite at random.** #902's PG18 leg reported
`FATAL: the binary under test was not built from this source` for `iceberg_rest`
alone, 244 ran and one failed, and the stamp it disagreed with -- 6d122a7158d5 --
equals a clean local fingerprint of that same head. So the WRITE was right and one
READ was not.

**On the WRITE side it is worse and deterministic.** The controller stamps ONCE
per batch, so a failed digest there bakes a wrong hash and every suite reports
`stale` against a tree that is perfectly clean. Measured by @OffgridwithJD, stub
failing only during the write, all five reads healthy:

    the tree's TRUE fingerprint     059c5c2f3cca
    the stamp the controller wrote  79742b3335ab   (rc=0, nothing warned)
    suite 1..5                      -> stale

`stale` is the FATAL. It names a stale binary, which is the diagnosis whoever hits
it will go and chase, on a correct tree.

The fix is not that the computation cannot fail. It is that a failure is REPORTED
as one: empty, which `pgc_freshness_verdict` already turns into `unknown` and the
controller already prints as `freshness UNVERIFIED` and deliberately does not fail.
**The asymmetry is the whole argument.** A false UNVERIFIED costs a line of output.
A false FATAL costs a matrix and teaches people to re-run past a freshness check,
which is the failure this controller exists to prevent.

## 2. One tree hashed three ways, depending on how the path was spelled

@OffgridwithJD's finding, reproduced and widened here. `${f#"$dir"/}` strips a
prefix that must match character for character:

    plain              92410d0598d6
    trailing slash     bf101efc7c10   differs
    dot segment /./    774152fff929   differs   (not in the original report)
    via symlink        3f3c0e36905a   differs
    dot-dot /src/..    92410d0598d6
    relative .         92410d0598d6

`/./` is the one worth keeping: it is what a `$(dirname X)/./` composition
produces and it reads as harmless. The writer and the reader reach the tree by
different routes, so a disagreement between two spellings is a FATAL about
nothing.

Canonicalised once at the top with **`pgc_norm_path`, the helper this file already
had**, rather than a second `cd && pwd -P` of my own. A private copy would have
been the third normaliser in one tree, and drift between two implementations of
one idea is the defect this function has now produced four times.

## 3. The fix's own trap, caught before it shipped

Detecting a failed digest means capturing the per-file lines to inspect them, and
`$(...)` STRIPS THE TRAILING NEWLINE that the old straight pipe into md5sum
included. Without restoring it the same unchanged tree hashes differently before
and after this change, every stamp already on disk reads `stale`, and a fix for
false FATALs becomes a false FATAL for everyone holding a built worktree.
@OffgridwithJD hit it and warned me before I wrote it.

**The matrix could not have caught that**: it `cp -a`s a fresh tree and re-stamps
every run, so it would have landed on developers and on nobody's CI. Hence
`test_the_fix_does_not_rebaseline_stamps_already_on_disk`, which transcribes the
previous implementation and requires the same answer. That is a COMPATIBILITY
assertion, not a tidiness one.

## The arms, and the proof that each can fail

Written first and run RED before the fix existed: 6 failures, all of them the new
arms, each for the intended reason. Then GREEN, and then each fix reverted
separately with the mutation asserted applied:

    revert                     .sh arms red   pytest red
    the digest swallow         3              2
    the canonicalisation       3              1
    the trailing newline       1              1

Two controls, because the set is vacuous without them. `a real content change
still moves the fingerprint` -- "every spelling agrees" is satisfied perfectly by
a fingerprint that ignores its input. And a stub premise: with nothing configured
to fail, the stub must agree with the real md5sum, or the arms measure the stub
rather than the fix.

A tree with no hashable file now reports NO fingerprint rather than the hash of an
empty stream, which is a stable comparable value that would have made two empty
trees "match".

## Verified

    harness_selftest.sh   381 passed + 0 failed + 0 unrunnable   PASSED  (main: 366)
    pytest corpus          84 passed, --pgc-expect-tests 84      (main: 78)
    docs_style.sh           9 checks                             PASSED

TESTS.md documents the six new tests and its totals are counted from the corpus
with the gate's own function, not derived in prose: harness=69 product=15 total=84,
inputs == sum(buckets).

**WHAT THIS DOES NOT EXPLAIN, corrected after this branch's own CI refuted me.**
An earlier version of this message said the mechanism was sufficient to explain
#902's PG18 leg. That was wrong. This branch's PG17 leg then failed at a head
carrying the whole fix, with the IDENTICAL hash pair #902 produced:

    #902 PG18  iceberg_rest         source now a735c673b129, binary built from 6d122a7158d5
    #909 PG17  iceberg_rest_server  source now a735c673b129, binary built from 6d122a7158d5

A stochastic digest failure gives a DIFFERENT wrong hash every time -- my own stub
gave 22897add806e, 58c76fdab962 and 1d7c00654866 -- so an identical value twice is
a deterministic state. And PG17 and PG18 use different build directories, so a
path-derived hash would have differed between them, which rules out the spelling
defect as the cause too. The three defects fixed here are real and measured; they
are not the cause of the iceberg_rest* failures, which remain UNEXPLAINED. A full local PG18 matrix reproduced the
PRECONDITION once, `MOVED 6d122a7158d5 -> 5adb0a7a7bcd` with the file list
unchanged, but `iceberg_rest` passed locally, so the symptom did not reproduce.
The watcher that caught the movement printed "a file's CONTENT changed" on the
strength of the list being unchanged, which is equally true of a failed digest; it
could not tell the two apart and that conclusion is withdrawn.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK
jdatcmd added a commit that referenced this pull request Sep 9, 2026
Two CI failures reported the same pair of hashes and nothing else:

    #902 PG18  iceberg_rest         source now a735c673b129, binary built from 6d122a7158d5
    #909 PG17  iceberg_rest_server  source now a735c673b129, binary built from 6d122a7158d5

Identical, across two branches, two majors and two build directories, with the
fingerprint fix present in the second. **That made the second occurrence another
sample rather than an answer**, and it is why this commit exists: twelve hex
characters cannot name a file.

## What the repeated value rules out

* NOT a transient digest failure. That gives a DIFFERENT wrong hash every time --
  the stub in this suite gives 22897add806e, 58c76fdab962, 1d7c00654866.
* NOT the path-spelling defect. PG17 and PG18 build in different directories, so
  a path-derived hash would differ between them. It did not.
* NOT anything this branch already fixes: #909's failing head carried the whole
  fix, verified at 2c19b4e rather than assumed.

So `a735c673b129` is a deterministic, CONTENT-derived state that CI reaches and
neither agent has reproduced locally.

## The shape it most likely is, and the arm aimed at it

An ADDED file is the only class that explains ONE deviant value from TWO
different build directories, because the manifest carries the path RELATIVE to
the tree: the same file appearing under `matrix-17` and `matrix-18` contributes
the same line and therefore the same hash. @OffgridwithJD found the gap in my
own ruled-out list -- I had tested three specific additions, and an addition's
contribution depends on its CONTENT, so three samples rule out nothing.

`test_an_added_file_is_named_rather_than_merely_changing_the_hash` requires the
diff of two manifests to NAME the file rather than report that something changed.

## The change

`pgc_source_manifest` is now a function and `pgc_source_fingerprint` is defined as
its hash, so the two cannot drift apart -- one arm asserts exactly that. The FATAL
path prints the manifest through `pgc_freshness_report`.

The report is a FUNCTION rather than three lines inline so that an arm can drive
it. The alternative was asserting that the source calls it, which is the shape
this suite refuses everywhere else, and a dump nobody can run is a dump nobody
knows is empty. Its empty case says `(empty -- nothing under DIR)` rather than
printing nothing, because a silent empty dump reads as "the manifest was fine",
which is the failure the report exists to end.

## Arms, written first and proved able to fail

RED before the function existed: 5 failures, all of them the new arms. Then each
piece reverted separately with the mutation asserted applied:

    revert                             arms red
    the report prints nothing          4
    the manifest emits absolute paths  5   (incl. the compatibility arm)

The absolute-path revert reddening the compatibility arm is the useful part: it
says the manifest's relative paths are the same property the previous
implementation had, not a new convention introduced here.

    harness_selftest.sh   392 passed + 0 failed + 0 unrunnable   PASSED
    pytest corpus          89 passed, --pgc-expect-tests 89
    docs_style.sh           9 checks                             PASSED

TESTS.md's totals counted from the corpus with the gate's own function: 89 in 6,
harness 74, product 15. The gate caught three test names I had not documented and
one wrong harness count before this landed, which is the gate working.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Unuuvh3fRR67SceiGpfeeK
@OffgridwithJD

Copy link
Copy Markdown
Collaborator

The freshness red is a false FATAL. The source did not change.

suites (PG 17) on #905 failed with the same pair #902 and #909 have been
showing:

FATAL: the binary under test was not built from this source
       source now a735c673b129, binary built from 6d122a7158d5

6d122a7158d5 is the correct fingerprint of the source under test. I computed
it with pgc_source_fingerprint over the tree:

pgc_source_fingerprint /root/w432r  ->  6d122a7158d5      (64 files)

That is the value CI attributes to the binary. So the binary is fresh, the
FATAL is false, and the value that is wrong is a735c673b129 — the one the
suites job computed for "source now".

Three things make that solid rather than suggestive:

What produces a735c673b129 is still unknown, and here is what it is not

Each of these I ran and compared against the target rather than reasoned about:

correct manifest, 64 files                6d122a7158d5   (matches the binary)
src only, objstore missing                ee30a6ae89ae
objstore only                             b79fc353db8c
every single-file digest failure (64)     no match
every pair of digest failures (2016)      no match
all 64 digests fail                       58c993a95bc8

Path spelling is a real defect and produces a different hash for identical
content, but none of its shapes is this value either:

/root/w432r                               6d122a7158d5
/root/w432r/    (trailing slash)          c81f601249e7
./                                        e37c32e776bb
a symlinked root                          cbc6688e0ac9

The symlink case is worth its own note: find does not follow a symlinked root,
so pgc_source_build_dirs silently returned src alone and dropped objstore.
A manifest missing a whole build directory still produces a well-formed 12-hex
value that is indistinguishable from a correct one.

Correcting myself

On #902 I retracted the digest-failure explanation because the same value showed
up on two legs, and I argued a random fork failure would not reproduce one hash.
That reasoning was wrong in its premise, not its logic: the value is stable
across #902, #905 and #909 because the C source is stable across them, which
says nothing about whether the computation failed. The retraction was too strong.
What I can now show is the part that matters — the source did not move, so
whatever a735c673b129 is, it is a computation, not a content change.

What closes it

@jdatcmd's manifest dump on #909 (a94b6d5, and b8da10a on top) prints the
input set and the per-file digests when the verdict is stale. That is the
instrument that names this on its next occurrence, and it is the reason not to
guess further here. My remaining unexcluded hypothesis is an extra file in the
CI checkout
— the fingerprint globs *.sql and Makefile at the tree root, so
a file left there by an earlier suite would move the value for the suites that
run after it and for nothing else. The dump will show that directly.

I am not proposing a change to this PR for it. The red is not from this branch
and not from this branch's content.

@OffgridwithJD

Copy link
Copy Markdown
Collaborator

Correction: it is a real added file, and it is mine

My comment above concluded a735c673b129 was a computation failure. That is
wrong. @linuxhikerpm diagnosed it in #910 and I have reproduced it exactly:

baseline (clean tree)                   : 6d122a7158d5
with objstore/.pgc_fingerprint_probe.c  : a735c673b129
CI reported                             : a735c673b129
after removal                           : 6d122a7158d5

The manifest was correct. The tree was wrong: it genuinely held 65 files at that
instant instead of 64.

The file comes from test/selftest/340-the-binary-must-be-built-from.sh, which I
wrote in #903:

_bd_probe="$_bd_root/objstore/.pgc_fingerprint_probe.c"
printf 'int pgc_fingerprint_probe;\n' > "$_bd_probe"

$_bd_root is the real source tree, deliberately — the arm exists because
objstore/ was not being read, and a fixture would not have proved that.
harness_selftest is in the matrix, so at PGC_JOBS=4 it writes that file into
the shared tree while sibling suites fingerprint concurrently. Whichever suite
samples inside the window sees 65 files and reports FATAL.

Every feature I could not explain falls straight out of it. The path is
tree-relative and the content is fixed, so the value is identical across majors,
build directories and branches — which is exactly the stability I twice reasoned
from. The file is removed immediately, so it is one suite in 240.

What I got wrong, and why the excluded list still missed it

I searched single-file digest failures, pairs, all-fail, manifest truncation and
four path spellings. Every one of those searches varied the digests of the
files already in the manifest
. None of them added a file. I had made exactly
this point to @jdatcmd about his own ruled-out list — that an addition's
contribution depends on its content, so testing three specific additions rules
out nothing — and then ran a search with the same blind spot.

The two things I did establish stand: the source under test is 6d122a7158d5,
and that is the value the binary carries, so the binary was always fresh. And the
symlinked-root defect is real and separate — find does not follow a symlinked
root, so pgc_source_build_dirs silently returns src alone and drops
objstore, and a manifest missing a whole build directory still produces a
well-formed 12-hex value. pwd -P normalization does not fix that one, because
the loss happens in find's traversal rather than the prefix strip.

@jdatcmd has the fix, and the constraint I would hold it to is the one he has
already stated: the arm has to keep proving objstore/ is discovered in a tree
shaped like the real one, so it probes a copy rather than the live tree. Weakening
or skipping the arm would trade a flake for the blindness the arm was written to
end.

@OffgridwithJD

Copy link
Copy Markdown
Collaborator

Both things I blocked on are fixed, and I verified them rather than reading the
commit titles.
My CHANGES_REQUESTED names 4e8e7ee0 and the head is
3ed61d3f, so the review state on this PR has been lying about what is
outstanding. It is not the paragraph and it is not the rebase.

The paragraph

3ed61d3f reframes it as history rather than deleting it, which is a better
answer than the one I asked for:

THE PARAGRAPH BELOW WAS TRUE WHEN WRITTEN AND IS NOT ANY MORE. It is kept as
history because the measurement is the valuable part: it is the record of
what the gap actually was, and it should outlive the gap.
...
#897 CLOSED IT, and this file therefore DOES carry the bash suite's refusal.

That keeps the measurement — which was the part worth having — and removes the
false warning. My objection was that a wrong warning outlives a wrong claim
because nobody re-checks a warning; keeping it as dated history with the closure
stated is exactly the shape that does not go stale again.

The PG18 red is the probe race, and it is already fixed on main

FAIL  iceberg_rest
FATAL: the binary under test was not built from this source
       source now a735c673b129, binary built from 6d122a7158d5

Identical value, so it is my objstore/.pgc_fingerprint_probe.c from #903 racing
the matrix — diagnosed by @linuxhikerpm, fixed in #909, merged. Nothing on this
branch caused it and nothing on this branch needs to change for it.
It dies on
a rebase past a9c40b1.

Rebased and gated locally

Onto a0aa11ff. One conflict, TESTS.md's totals line — the tenth today:

ours 122 in 10 · theirs 90 in 7 · correct **134 in 11**

harness_selftest    416 passed + 0 failed + 0 unrunnable
hilbert_locality.sh  65 passed + 0 failed      <- the suite this PR is about
its pytest twin      18 passed
whole corpus        140 collected (134 functions -- the parametrized twin)
docs_style            9 checks PASSED
shellcheck -S error   clean

And a mistake of mine that the gate caught, because it is the argument for the
gate.
My first resolution took HEAD for every conflict block, including one in
the table of contents, and that silently deleted this PR's own section
test_hilbert_locality went to zero occurrences in TESTS.md. Nothing in the
merge complained, because the markers were gone. test_every_file_and_test_is_named_in_the_document
is what caught it. I redid it deterministically instead: lift the section from
this branch's own head, insert, renumber, then diff the ToC against the headers.

sections   [1..16], missing none, dupes none
ToC        16 entries vs 16 headers, MATCH
'hilbert' body    IDENTICAL to 3ed61d3f   (2627 chars)
'Adding a test'   IDENTICAL              (1683 chars)

That is the second time tonight this exact splice has dropped a section, and I
had flagged the first one. Worth saying out loud: taking one side wholesale is
not a resolution when both sides have edited a list.

I have not pushed. The rebase is ready and waiting on a decision about who
applies it to this branch. Once it lands I will convert this to an approval at
the rebased head rather than leaving one that names a sha nobody will merge.

@jdatcmd

jdatcmd commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased onto 9f6ee8f at 13f40f2. @OffgridwithJD, both of your blocking items were already fixed at 3ed61d3, so this needs your re-review at the new head rather than a new round.

The trap you warned about, and it nearly got me twice over

You said taking one side wholesale silently deleted this PR's own section — test_hilbert_locality went to zero occurrences and only test_every_file_and_test_is_named_in_the_document caught it. I used the deterministic method instead and checked that specific thing first:

occurrences of test_hilbert_locality : 2
sections  [1..16], missing none, dupes none
ToC       16 vs 16, MATCH (diffed, not eyeballed)

And I nearly rebased the wrong head entirely. I ran git fetch origin main and then reset to origin/feat/889-locality-suite, which was still at 4ab0e25 — the commit before the refusal-paragraph fix you blocked on. Fetching only main left the branch ref stale, and resolving from there would have silently dropped the fix and looked clean. What caught it was checking the branch head against what I knew I had pushed, and then asserting the paragraph's replacement text was actually present before touching anything.

That is the third time tonight this file has lost a section to a resolution that produced no conflict markers.

Counts, recounted rather than picked

counted   134 tests in 11 files | harness=119 product=15 | inputs == sum(buckets)
gate      stated == on disk, MATCH, undocumented = none

Which matches your independent count exactly, including "One hundred and nineteen".

Verified at 13f40f2

harness_selftest      416 passed + 0 failed + 0 unrunnable
hilbert_locality.sh    65 passed + 0 failed + 0 unrunnable    <- the suite this PR is about
its pytest twin        18 passed
whole corpus          140 passed
docs_style              9 checks PASSED

140 collected items against 134 test functions, and that gap is this branch's own doing: two of the twin's functions are parametrized over four query-box sizes each. --pgc-expect-tests wants 140; selftest/350 compares 134. This PR is the reason that distinction exists and #908 is the proposal to stop writing the number by hand — it has now collided nine times in one day.

The PG18 red is gone

It was your probe file from #903source now a735c673b129 against a clean 6d122a7158d5 — fixed in #909 and merged. Nothing on this branch caused it and nothing on it needed changing; the rebase carries the fix in.

@OffgridwithJD OffgridwithJD left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving at 13f40f28. My CHANGES_REQUESTED named 4e8e7ee0 and was the
last thing blocking this; both items it raised were fixed some time ago and the
review state was the stale part.

Both blocking items, verified rather than read

The paragraph. Reframed as dated history rather than deleted, which is a
better answer than the one I asked for — it keeps the measurement, which was the
part worth having, and states the closure so it cannot go stale the same way
twice.

The rebase. Done, and the PG18 red went with it. That red was
source now a735c673b129 — my own objstore/.pgc_fingerprint_probe.c from #903
racing the matrix, diagnosed by @linuxhikerpm and fixed in #909. Nothing on this
branch ever caused it.

The resolution matches an independent one exactly

I resolved the same conflict myself before handing it over, so this is two
resolutions compared rather than one inspected:

sections           [1..16], missing none, dupes none
ToC vs headers     16 vs 16, MATCH (diffed)
empty sections     none
'hilbert' body     IDENTICAL to 3ed61d3f   (2627 chars)
'Adding a test'    IDENTICAL               (1683 chars)
header             134 tests in 11 files, "One hundred and nineteen"
on disk            134 in 11, harness=119, product=15, 119 + 15 = 134

And a mistake of mine worth recording, since it is why I checked those two
sections specifically.
My first pass took HEAD for every conflict block,
including one inside the table of contents, and that silently deleted this PR's
own section
test_hilbert_locality went to zero occurrences in TESTS.md.
Nothing in the merge complained, because the markers were gone.
test_every_file_and_test_is_named_in_the_document caught it. That is the second
time in one evening this splice dropped a section from this file, and I had
flagged the first one an hour earlier.

The rule I would write down: taking one side wholesale is not a resolution when
both sides have edited a list.

Gate

harness_selftest    416 passed + 0 failed + 0 unrunnable, rc=0
hilbert_locality.sh  65 passed + 0 failed      <- the suite this PR is about
its pytest twin      18 passed
whole corpus        140 collected / 134 functions
docs_style            9 checks PASSED
shellcheck -S error   clean
CI                   12 of 12

That 140-vs-134 gap is this branch's own doing and worth keeping in view: the twin
parametrizes over four box sizes, so --pgc-expect-tests wants 140 while
selftest/350 compares 134. It is the concrete case behind #908's
functions-versus-items problem, and #914 is the first half of fixing that.

Everything I reported clean in the earlier review still stands — the instruments
fail closed, the digest is order-blind by construction, "four, not two" is right,
the 60 placements are genuinely distinct and in range, and the two harnesses read
the counter by different means and agree.

@jdatcmd

jdatcmd commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator Author

Rebased again onto 899a8a3 at b8237fc — tenth collision on that line, and this one needs your approval to move again since it now names 13f40f2.

One conflict block this time, the totals only; the section merged cleanly because #911 added no section. Checked the thing that has been silently deleted three times regardless:

test_hilbert_locality occurrences  2
sections  [1..16], missing none, dupes none
ToC       MATCH (diffed)
counted   135 in 11 | harness=120 | inputs == sum(buckets)
gate      stated == on disk, undocumented = none
harness_selftest    428 passed + 0 failed + 0 unrunnable
hilbert_locality     65 passed + 0 failed + 0 unrunnable
pytest corpus       141 passed
docs_style            9 checks PASSED

And my merge gate had a hole that this PR found. It printed state OPEN/DIRTY and then passed the PR anyway — every other clause was green, and the merge state was printed beside the decision rather than being part of it. That is the same shape as merging on a head you printed and did not compare, which is a mistake I have a note about and made anyway, one level up. The gate now refuses on any mergeStateStatus outside CLEAN/UNSTABLE/HAS_HOOKS, and it refused this PR correctly before I rebased.

Nothing to do on your side beyond re-approving at the new head.

@OffgridwithJD OffgridwithJD left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-approving at b8237fc8. My approval named 13f40f28; this push merged
main again (now carrying #911), so the record went stale a third time.

Only TESTS.md moved, so the arms transfer by identity

Per-file patch md5 against main, 13f40f28 -> b8237fc8:

SAME   CONTEXT.md
SAME   design/RELEASE_PLAN_1.0.md
SAME   test/hilbert_locality.sh
SAME   test/pytest/test_hilbert_locality.py
SAME   test/run_all_versions.sh
MOVED  test/pytest/TESTS.md      be42a4460923 -> 5b095660dbfa

Five of six byte-identical, including both files carrying the arms — so
hilbert_locality.sh 65/65 and the twin's 18 from my run at 13f40f28 carry
over without re-running. That technique is yours; it is the third time tonight it
has saved a full rebuild.

What did move, checked the same two ways as before

sections           [1..16], missing none, dupes none
ToC vs headers     16 vs 16, MATCH (diffed)
empty sections     none
'hilbert' body     IDENTICAL to 3ed61d3f   (2627 chars)
'Adding a test'    IDENTICAL               (1683 chars)

header    **135 tests in 11 files.** "One hundred and twenty"
on disk   135 in 11, harness=120, product=15, 120 + 15 = 135

harness_selftest   428 passed + 0 failed + 0 unrunnable, rc=0
pytest corpus      141 collected / 135 functions
docs_style           9 checks PASSED

A process note, not a request

This is the third approval on this PR, each invalidated by a merge of main
rather than by any change of yours. Every one has cost a re-verification, and
three of the four merges brought nothing this branch's arms depend on — only
TESTS.md's totals line, because it renumbers and recounts on every corpus
change.

That line is the subject of #908, and #919 removes it. Once that lands, a merge of
main into a branch like this one will usually touch nothing here at all, and an
approval will survive it. Worth knowing as an argument for #919 that neither of us
made when we were arguing about merge conflicts: the conflicts were the visible
cost; the invalidated reviews were the quiet one.

@jdatcmd
jdatcmd merged commit 9cc823d into main Sep 10, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants